#include "CDGWidgetGL.h"

#include <sys/types.h> 
#include <sys/wait.h> 
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <mythtv/oldsettings.h>
#include <mythtv/lcddevice.h>

#include <qfile.h>
#include <qimage.h>
#include "constants.h"
#include "decoder.h"

static int m_texMaxDimension;

CDGWidgetGL::CDGWidgetGL(QWidget *parent, const char *name )
    : QGLWidget( parent, name ),
      m_showFrame(true)
{
    cerr << "Using CDGWidget: GL" << endl;
    hide();
    setAutoBufferSwap(true);

    this->setGeometry(0, 0, parent->width(), parent->height());

    //Frame
    m_showFrame = gContext->GetNumSetting("CdgUseFrame");
    cerr << "Draw a frame: " << m_showFrame << endl;
}


CDGWidgetGL::~CDGWidgetGL()
{
    cerr << "exit" << endl;
    stop();
}


void CDGWidgetGL::initializeGL(void)
{
    cerr << "initializeGL" << endl;

    // Enable Texture Mapping
    glEnable(GL_TEXTURE_2D);
    // Clear The Background Color
    glClearColor(0.0, 0.0, 0.0, 1.0f);

    // Turn Blending On
    glEnable(GL_BLEND);

    // Blending Function For Translucency Based On Source Alpha Value
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Enable perspective vision
    glClearDepth(1.0f);

    //Texturesize limit
    GLint param;
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &param);
    m_texMaxDimension = param;
}


void CDGWidgetGL::resizeGL(int w, int h)
{
    cerr << "resizeGL w:" << w << " h:" << h << endl;

    // Reset The Current Viewport And Perspective Transformation
    glViewport(0, 0, (GLint)w, (GLint)h);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

}

void CDGWidgetGL::CreateCDG(void)
{
    int height = GetNearestGLTextureSize(216);
    int width = GetNearestGLTextureSize(300);
    m_cdgTextWidth = width;
    m_cdgTextHeight = height;

    cerr << "CreateCDG: W: " << 300 << "->" <<  width << " H: " << 216 << "->" << height << endl;

//     // Transparent image
//     unsigned char tmp[cdgImageHeight][cdgImageWidth][4];
//     memset(tmp, 0, sizeof(tmp));

    //Create CDG
    glGenTextures(1, &m_cdgTexture);
    glBindTexture(GL_TEXTURE_2D, m_cdgTexture);

    // Generate the texture (inc. alpha)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, NULL);

    // No texture-warp and enable linear filtering 
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    //Create Frame
    glGenTextures(1, &m_frameTexture);
    glBindTexture(GL_TEXTURE_2D, m_frameTexture);

    // Generate the texture (inc. alpha)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, NULL);

    // No texture-warp and enable linear filtering 
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

}


void CDGWidgetGL::paintGL(void)
{
    glDisable(GL_DEPTH_TEST);
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    m_bkg.paintGL();

    if(m_showFrame)
       paintCDGFrame();
    
    paintCDG();
    
    m_clock.paintGL();
    m_info.paintGL();

    if (glGetError())
        VERBOSE(VB_GENERAL, "OpenGL error detected");
}


inline void CDGWidgetGL::paintCDGFrame(void)
{
    // Frame
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glBindTexture(GL_TEXTURE_2D, m_frameTexture);
    
    glTexSubImage2D(GL_TEXTURE_2D, 0, 
                    0, 0,
                    300,216,
                    GL_RGBA, GL_UNSIGNED_BYTE,
                    &m_cdg.frame);
    
    glBegin(GL_QUADS);
    {
        glColor4f(1.0f, 1.0f, 1.0f, 0.8f);            
        
        glTexCoord2f(0.5f/m_cdgTextWidth,         0.5f/m_cdgTextHeight);         glVertex2f(-1.0,  1.0);
        glTexCoord2f((300.f-0.5f)/m_cdgTextWidth, 0.5f/m_cdgTextHeight);         glVertex2f( 1.0,  1.0);
        glTexCoord2f((300.f-0.5f)/m_cdgTextWidth, (216.f-0.5f)/m_cdgTextHeight); glVertex2f( 1.0, -1.0);
        glTexCoord2f(0.5f/m_cdgTextWidth,         (216.f-0.5f)/m_cdgTextHeight); glVertex2f(-1.0, -1.0);
        
    }
    glEnd();

}

inline void CDGWidgetGL::paintCDG(void)
{
    // CDG
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glBindTexture(GL_TEXTURE_2D, m_cdgTexture);
    
    glTexSubImage2D(GL_TEXTURE_2D, 0, 
                    0, 0,
                    300,216,
                    GL_RGBA, GL_UNSIGNED_BYTE,
                    &m_cdg.image);
    
    glBegin(GL_QUADS);
    {
         glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
         
         glTexCoord2f(0.5f/m_cdgTextWidth,         0.5f/m_cdgTextHeight);         glVertex2f(-1.0,  1.0);
         glTexCoord2f((300.f-0.5f)/m_cdgTextWidth, 0.5f/m_cdgTextHeight);         glVertex2f( 1.0,  1.0);
         glTexCoord2f((300.f-0.5f)/m_cdgTextWidth, (216.f-0.5f)/m_cdgTextHeight); glVertex2f( 1.0, -1.0);
         glTexCoord2f(0.5f/m_cdgTextWidth,         (216.f-0.5f)/m_cdgTextHeight); glVertex2f(-1.0, -1.0);
    }
    glEnd();
}

void CDGWidgetGL::stop()
{
    if (m_timer)
    {
        m_timer->stop();
        delete m_timer;
        m_timer = 0;
    }

    stopDecoder();

    if (m_output)
    {
        delete m_output;
        m_output = 0;
    }
    
    if(m_input)
    {
        delete m_input;
        m_input = 0;
    }

    hide();

    //LCD
    LCD *lcd = LCD::Get();
    if (lcd)
       lcd->switchToTime();
    
    m_isactive = false;
    m_cdg.stop();
    m_bkg.clear();
    m_info.clear();

    if (m_cdgTexture)
       glDeleteTextures(1, &m_cdgTexture);
    if (m_frameTexture)
       glDeleteTextures(1, &m_frameTexture);
}

bool CDGWidgetGL::play(const QString& filename, bool useTransp, bool useTransBkg, bool useTransBorder)
{
    cout << "Play using GL support: " << filename << endl;

    //Setup
    m_parsed = 0;
    m_isplaying = false;
    m_isactive = false;

    //Open CDG file
    if(!m_cdg.start(std::string(filename.latin1()),useTransp,useTransBkg,useTransBorder))
       return false;
    
    //Save info
    m_cdgFile = filename;

    //Open MP3 file
    QString mp3file = filename.left(filename.findRev('.'));
    QFile test(mp3file+".mp3");
    if (test.exists())
       mp3file += ".mp3";
    else
       mp3file += ".MP3";
    m_input = new QFile(mp3file);

    //CDG
    CreateCDG();

    //Background
    printf("Loading backgroundimage\n");
    m_bkg.create();


    if (!m_output)
        openOutputDevice(this);
   
    if (m_decoder && !m_decoder->factory()->supports(mp3file))
        m_decoder = 0;

    if (!m_decoder) 
    {
        printf("Support? %s\n",mp3file.data());
        m_decoder = Decoder::create(mp3file, m_input, m_output);

        if (!m_decoder) 
        {
            printf("decoder: unsupported fileformat\n");
//            stopAll();
            return false;
        }

        m_decoder->setBlockSize(globalBlockSize);
        m_decoder->addListener(this);
    } 
    else 
    {
        m_decoder->setInput(m_input);
        m_decoder->setFilename(mp3file);
        m_decoder->setOutput(m_output);
    }

    if (m_decoder->initialize()) 
    {
        if (m_output)
        {
            m_output->Reset();
        }

        printf("decoder start\n");
        m_decoder->start();
    }

    //Create timer if needed and start animation
    if(!m_timer) {
        m_timer = new QTimer( this );
        connect( m_timer, SIGNAL(timeout()), this, SLOT(nextPos()) );
    }
    m_timer->start(animTimer);

    //Initate clock
    m_clock.init(width(), height(), m_cdg.getLength());

    show();    
    m_isplaying = !m_isplaying;
    m_isactive = true;
    return true;
}


void CDGWidgetGL::info()
{
    QString info = "CDG:\n"+m_cdgFile+"\n\nMp3";
    m_info.show(info);
}


void CDGWidgetGL::nextPos()
{
    int msec = m_output->GetAudiotime();

    // Read 3 pkgs each 10ms
    int read = ((msec*3)/10)-m_parsed;
    
    //Only FFWD right now...
    if(read<0) return;

    m_cdg.handlePackets(read);
    m_parsed += read;

    //Create CDG
    m_cdg.generateImage();

    //Create frame
    if(m_showFrame)
       m_cdg.generateFrame();

    m_clock.setTime(m_cdg.getCurrent());
    
//     repaint(false);
    updateGL();
}


void CDGWidgetGL::customEvent(QCustomEvent *event)
{
    switch ((int)event->type()) 
    {
        case OutputEvent::Playing:
        {
            printf("Playing stream.\n");
            break;
        }

        case OutputEvent::Buffering:
        {
            printf("Stream buffering.\n");
            break;
        }

        case OutputEvent::Paused:
        {
            printf("Stream paused.\n");
            break;
        }

        case OutputEvent::Info:
        {
            OutputEvent *oe = (OutputEvent *) event;

            int eh, em, es, rs, ts;
            rs = ts = oe->elapsedSeconds();

            eh = ts / 3600;
            em = (ts / 60) % 60;
            es = ts % 60;

            QString time_string;
            
            if (eh > 0) 
               time_string.sprintf("%d:%02d:%02d", eh, em, es);
            else 
               time_string.sprintf("%02d:%02d", em, es);                   
            
            printf("%s\n",time_string.data());
            break;
        }
        case OutputEvent::Error:
        {
            printf("Output error.\n");
            break;
        }
        case DecoderEvent::Stopped:
        {
            printf("Stream stopped.\n");
            break;
        }
        case DecoderEvent::Finished:
        {
            printf("Finished playing stream.\n");
            printf("Lets exit.\n");
            stop();
            break;
        }
        case DecoderEvent::Error:
        {
            printf("Decoder error.\n");
            break;
        }
    }

    QWidget::customEvent(event);
}

int CDGWidgetGL::GetNearestGLTextureSize(int v)
{
    int n = 0, last = 0;
    int s;

    for (s = 0; s < 32; ++s)
    {
        if (((v >> s) & 1) == 1)
        {
            ++n;
            last = s;
        }
    }

    if (n > 1)
        s = 1 << (last + 1);
    else
        s = 1 << last;

    return min(s, m_texMaxDimension);
}
